package com.smallkingson.ylib.db;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Pair;

import com.smallkingson.ylib.core.Yr;
import com.smallkingson.ylib.core.utils.YBeanUtils;
import com.smallkingson.ylib.db.bean.KeyObtainable;

/**
 * 针对bean提供方便的增删查改功能 在
 *
 * @author yorun
 */
public class YSQLites {

    public static boolean debug = true;

    /**
     * 添加表
     *
     * @param helper
     * @param beanClass 表的结构会根据这个bean的变量来定义表的字段
     */
    public static void createTable(SQLiteOpenHelper helper, Class<?> beanClass) {
        createTable(helper.getWritableDatabase(), beanClass);
    }

    public static void createTable(SQLiteOpenHelper helper, Class<?> beanClass, CreateTableHandler createTableHandler) {
        createTable(helper.getWritableDatabase(), beanClass, createTableHandler);
    }

    public static void createTable(SQLiteDatabase db, Class<?> beanClass) {
        createTable(db, beanClass, null);
    }


    public static void createTable(SQLiteDatabase db, Class<?> beanClass, CreateTableHandler createTableHandler) {

        final StringBuilder sb = new StringBuilder();

        String name = beanClass.getName();

        name = name.substring(name.lastIndexOf(".") + 1);

        name = name.substring(name.lastIndexOf("$") + 1);

        sb.append("create  table if not exists " + name + " (").append(
                "_id integer primary key AUTOINCREMENT ,");
        Method[] methods = beanClass.getMethods();

        for (Method method : methods) {

            String methodName = method.getName();

            if (methodName.startsWith("get")) {

                if (methodName.equals("getClass")) {
                    continue;
                }

                final String fieldName = YBeanUtils
                        .getFieldNameFromMethod(method);

                sb.append("'" + fieldName + "' ");

                Class<?> returnType = method.getReturnType();

                if (createTableHandler == null || !(createTableHandler.onSetType(returnType, fieldName, sb) && sb.append
                        (",") != null)) {

                    YBeanUtils.setTypeWatcher(returnType, new YBeanUtils.TypeWatcher() {
                        @Override
                        public void onString() {
                            sb.append("TEXT,");
                        }

                        @Override
                        public void onLongObj() {

                            sb.append("INT8,");
                        }

                        @Override
                        public void onLong() {

                            sb.append("INT8,");
                        }

                        @Override
                        public void onIntObj() {

                            sb.append("INTEGER,");
                        }

                        @Override
                        public void onInt() {

                            sb.append("INTEGER,");
                        }

                        @Override
                        public void onFloatObj() {

                            sb.append("REAL,");
                        }

                        @Override
                        public void onFloat() {

                            sb.append("REAL,");
                        }

                        @Override
                        public void onBooleanObj() {

                            sb.append("INTEGER,");
                        }

                        @Override
                        public void onBoolean() {

                            sb.append("INTEGER,");
                        }

                        @Override
                        public void onDoubleObj() {
                            sb.append("REAL,");
                        }

                        @Override
                        public void onDouble() {
                            sb.append("REAL,");
                        }
                    });

                }
            }
        }
        String sql = sb.substring(0, sb.lastIndexOf(","));

        sql = sql + ")";

        if (debug) {
            Yr.i("创建表", sql);
        }
        db.execSQL(sql);
        // db.close();
    }


    public static interface CreateTableHandler {

        boolean onSetType(Class<?> type, String fieldName, StringBuilder sb);
    }

    /**
     * 删除表
     *
     * @param helper
     * @param beanClass 表的结构会根据这个bean的变量来定义表的字段
     */
    public static void dropTable(SQLiteOpenHelper helper, Class<?> beanClass) {
        dropTable(helper.getWritableDatabase(), beanClass);
    }

    public static void dropTable(SQLiteDatabase db, Class<?> beanClass) {

        final StringBuilder sb = new StringBuilder();

        String name = beanClass.getName();

        name = name.substring(name.lastIndexOf(".") + 1);

        name = name.substring(name.lastIndexOf("$") + 1);

        sb.append("drop table if exists " + name);

        String sql = sb.toString();
        if (debug) {
            Yr.i("删除表", sql);
        }
        db.execSQL(sql);
        // db.close();
    }

    /**
     * 存在就更新 不存在就插入
     *
     * @param helper
     * @param bean
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws NoSuchFieldException
     */
    public static void save(SQLiteOpenHelper helper, KeyObtainable bean) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
        if (exists(helper, bean)) {
            update(helper, bean);
        } else {
            insert(helper, bean);
        }
    }

    public static boolean exists(SQLiteOpenHelper helper, KeyObtainable bean, Class aClass) {
        String where = YSqliteUtils.getWhere(bean);
        int count = YSQLites.getCount(helper.getWritableDatabase(), aClass, where);
        return count > 0;
    }

    public static boolean exists(SQLiteOpenHelper helper, KeyObtainable bean) {
        return exists(helper, bean, bean.getClass());
    }

    public static boolean insert(SQLiteOpenHelper helper, String tableName,
                                 Map<String, Object> keyValues) {
        SQLiteDatabase db = helper.getWritableDatabase();
        return insert(db, tableName, keyValues);
    }

    private static boolean insert(SQLiteDatabase db, String tableName,
                                  Map<String, Object> keyValues) {

        ContentValues values = new ContentValues();
        Set<Map.Entry<String, Object>> entries = keyValues.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            putEntryToContentValues(entry, values);
        }
        if (debug) {
            Yr.i("插入数据", tableName, values);
        }
        long rowId = db.insertOrThrow(tableName, null, values);
        // db.close();
        if (rowId != -1) {
            return true;
        }
        return false;
    }

    /**
     * @param helper
     * @param bean   要插入的对象
     * @return 如果成功 返回true
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     */
    public static boolean insert(SQLiteOpenHelper helper, KeyObtainable bean)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        return insert(helper.getWritableDatabase(), bean);
    }

    @SuppressLint("DefaultLocale")
    public static boolean insert(SQLiteDatabase db, KeyObtainable bean)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {

        String tableName = bean.getClass().getSimpleName();
        return insert(db, bean, bean.getClass());
    }

    @SuppressLint("DefaultLocale")
    public static boolean insert(SQLiteOpenHelper helper, KeyObtainable bean, Class clazz)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        return insert(helper.getWritableDatabase(), bean, clazz);
    }

    @SuppressLint("DefaultLocale")
    public static boolean insert(SQLiteDatabase db, KeyObtainable bean, Class clazz)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {

        //判断是否存在
//        Pair<String, String> keyPair = bean.obtainKey();
//        int count = YSQLites.getCount(db, bean.getClass(),  keyPair.first + "='" + keyPair.second + "'");
//        if (count > 0) {
//            throw new IllegalAccessException("重复的值：" + keyPair.first +"="+ keyPair.second);
//        }
        Map<String, Object> map = null;
        Method[] methods = clazz.getMethods();
        map = new HashMap<>();
        // 把带有get名称的方法 的名字放到notNullGetMethodName 这个数组里面来
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.startsWith("get")) {
                // 过滤掉getClass方法
                if (methodName.equals("getClass")) {
                    continue;
                }
                Object fieldValue = method.invoke(bean);
                if (fieldValue != null) {
                    map.put("'" + YBeanUtils.getFieldNameFromMethod(method) + "'", fieldValue);
                }
            }
        }
        return insert(db, clazz.getSimpleName(), map);
    }


    private static void putEntryToContentValues(
            Map.Entry<String, Object> entry, ContentValues values) {

        if (entry.getValue() instanceof String) {
            values.put(entry.getKey(), entry.getValue().toString());
        }
        if (entry.getValue() instanceof Integer) {
            values.put(entry.getKey(),
                    Integer.parseInt(entry.getValue().toString()));
        }
        if (entry.getValue() instanceof Float) {
            values.put(entry.getKey(),
                    Float.parseFloat(entry.getValue().toString()));
        }
        if (entry.getValue() instanceof Boolean) {
            values.put(entry.getKey(),
                    Boolean.parseBoolean((entry.getValue().toString())));
        }
        if (entry.getValue() instanceof Double) {
            values.put(entry.getKey(),
                    Double.parseDouble((entry.getValue().toString())));
        }
        if (entry.getValue() instanceof Byte) {
            values.put(entry.getKey(),
                    Byte.parseByte((entry.getValue().toString())));
        }
        if (entry.getValue() instanceof Long) {
            values.put(entry.getKey(),
                    Long.parseLong((entry.getValue().toString())));
        }
        if (entry.getValue() instanceof Short) {
            values.put(entry.getKey(),
                    Short.parseShort((entry.getValue().toString())));
        }
    }

    /**
     * 删除某条记录
     *
     * @param helper
     * @param
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static void delete(SQLiteOpenHelper helper, Class<?> beanClass,
                              String where, String... args) throws NoSuchMethodException,
            IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        where = setParamsToSql(where, args);
        SQLiteDatabase db = helper.getWritableDatabase();
        String tableName = beanClass.getSimpleName();
        String sql = "delete from " + tableName + " where " + where;
        if (debug) {
            Yr.i("执行删除记录", sql);
        }
        db.execSQL(sql);
        // db.close();
    }

    public static void update(SQLiteOpenHelper helper, Object bean,
                              String where, String... updateColumns)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException {
        update(helper, bean, where, bean.getClass(), updateColumns);
        // db.close();
    }

    public static void update(SQLiteOpenHelper helper, Object bean,
                              String where, Class beanClass, String... updateColumns)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException {
        SQLiteDatabase db = helper.getWritableDatabase();
        String tableName = beanClass.getSimpleName();
        StringBuilder sb = new StringBuilder();
        sb.append("update ").append(tableName).append(" set ");
        for (String updateColunm : updateColumns) {
            String methodName = YBeanUtils.getGetMethod(updateColunm);
            Method method = beanClass.getMethod(methodName);
            Object value = method.invoke(bean);
            if (value == null) {
                continue;
            }
            sb.append("'" + updateColunm + "'")
                    .append("=");
            if (value instanceof String) {
                sb.append("'" + value + "'");
            } else if (value instanceof Boolean) {
                sb.append((Boolean) value ? "1" : "0");
            } else {
                sb.append(value);
            }
            sb.append(",");
        }
        String sql = sb.substring(0, sb.length() - 1);
        sql += YSqliteUtils.setParamsToSql(" where ?", where);
        if (debug) {
            Yr.i("更新数据 ", sql);
        }
        db.execSQL(sql);
        // db.close();
    }

    /**
     * @param helper
     * @param bean
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws NoSuchFieldException
     */
    public static void update(SQLiteOpenHelper helper, KeyObtainable bean)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException, NoSuchFieldException {

        Class<?> aClass = bean.getClass();
        update(helper, bean, aClass);
    }

    public static void update(SQLiteOpenHelper helper, KeyObtainable bean, Class aClass)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException, NoSuchFieldException {

        List<Method> getMethods = YBeanUtils.getGetMethods(aClass);
        String[] columnNames = new String[getMethods.size()];
        for (int i = 0; i < columnNames.length; i++) {
            columnNames[i] = YBeanUtils.getFieldNameFromMethod(getMethods.get(i));
        }
        YSQLites.update(helper, bean, YSqliteUtils.getWhere(bean), aClass, columnNames);
    }

    /**
     * @param helper
     * @param bean
     * @param opera  运算符 比如: "name,age@id" 会翻译成 update 表名 set
     *               name='bean.getName',age=bean.getAge where id=bean.getId
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    public static void update(SQLiteOpenHelper helper, Object bean, String opera)
            throws NoSuchMethodException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {
        SQLiteDatabase db = helper.getWritableDatabase();
        if (!opera.contains("@")) {
            throw new IllegalArgumentException("opera must contains @");
        }
        Class<? extends Object> beanClass = bean.getClass();
        String tableName = beanClass.getSimpleName();
        String sql = null;
        StringBuilder sb = new StringBuilder();
        sb.append("update ").append(tableName).append(" set ");
        String[] split = opera.split("@");
        String[] setColumns = split[0].split(",");
        if (split.length == 1) { // set,set@
            append(sb, bean, setColumns);
            sql = sb.substring(0, sb.length() - 1);
        } else {

            String[] whereColumns = split[1].split(",");
            if (TextUtils.isEmpty(split[0])) {// @where
                List<String> readableFieldNames = YBeanUtils
                        .getReadableFieldNames(beanClass);
                String[] fields = new String[readableFieldNames.size()];
                readableFieldNames.toArray(fields);
                append(sb, bean, fields);
            } else {// set,set@where
                append(sb, bean, setColumns);
            }
            sb.append(" where ");
            append(sb, bean, whereColumns);
        }
        sql = sb.toString();
        if (debug) {
            Yr.i("更新数据 ", sql);
        }
        db.execSQL(sql);
        // db.close();
    }

    private static void append(StringBuilder sb, Object bean, String[] columns)
            throws NoSuchMethodException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {
        Class<? extends Object> beanClass = bean.getClass();
        for (int i = 0; i < columns.length; i++) {
            String updateColunm = columns[i];
            String methodName = YBeanUtils.getGetMethod(updateColunm);
            Method method = beanClass.getMethod(methodName);
            Object value = method.invoke(bean);
            sb.append(updateColunm).append("=");
            Class<? extends Object> type = value.getClass();
            if (type == String.class) {
                sb.append("'" + sqliteEscape(value.toString()) + "'");
            } else if (type == boolean.class || type == Boolean.class) {
                sb.append(((Boolean) value) ? "1" : "0");
            } else {
                sb.append(value.toString());
            }
            if (i < columns.length - 1) {
                sb.append(",");
            }
        }
    }

    public static <T> T selectOne(SQLiteOpenHelper helper,
                                  Class<T> beanClass, String where, String... selectionArgs)
            throws Exception {
        List<T> select = select(helper, beanClass, where, selectionArgs);

        if (select == null || select.size() == 0) {
            return null;
        }
        return select.get(0);
    }

    public static <T> List<T> select(SQLiteOpenHelper helper,
                                     Class<T> beanClass)
            throws Exception {
        return select(helper, beanClass, "1=1", new Object[]{});
    }

    public static <T> List<T> select(SQLiteOpenHelper helper,
                                     Class<T> beanClass, String where)
            throws Exception {
        return select(helper, beanClass, where, new Object[]{});
    }

    public static <T> List<T> select(SQLiteOpenHelper helper,
                                     Class<T> beanClass, String where, Object[] selectionArgs)
            throws Exception {

        // 健壮性
        if (where == null) {
            throw new IllegalArgumentException("sql语句为 null");
        }
        if (beanClass == null) {
            throw new IllegalArgumentException("BeanClass为 null");
        }
        // 拼凑把sql中参数填满
        where = setParamsToSql(where, selectionArgs);
        String sql = "select * from " + beanClass.getSimpleName() + " where "
                + where;
        return select(helper, sql, beanClass);
    }

    public static <T> List<T> select(SQLiteOpenHelper helper, String sql,
                                     Class<T> beanClass) throws Exception {

        // 健壮性
        if (beanClass == null) {
            throw new IllegalArgumentException("BeanClass为 null");
        }
        // 获取数据库对象
        SQLiteDatabase db = helper.getReadableDatabase();
        // 获取bean的所有字段
        List<T> list = new LinkedList<T>();

        if (debug) {
            Yr.i("查询", sql);
        }
        Cursor cursor = db.rawQuery(sql, null);

        while (cursor.moveToNext()) {
            T t = YSqliteUtils.parseCursorToBean(cursor, beanClass);
            list.add(t);
        }
        // 释放资源
        cursor.close();
        // db.close();
        return list;
    }

    public static String sqliteEscape(String keyWord) {
        // 正则需要转义字符：'$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{',
        // '}', '|'
        // keyWord = keyWord.replaceAll("/", "//");
        // keyWord = keyWord.replaceAll("'", "''");
        // keyWord = keyWord.replaceAll("[", "/[");
        // keyWord = keyWord.replaceAll("]", "/]");
        // keyWord = keyWord.replaceAll("%", "/%");
        // keyWord = keyWord.replaceAll("&", "/&");
        // keyWord = keyWord.replaceAll("_", "/_");
        // keyWord = keyWord.replaceAll("(", "/(");
        // keyWord = keyWord.replaceAll(")", "/)");

        // keyWord = keyWord.replaceAll("\\/", "\\//");
        keyWord = keyWord.replaceAll("'", "''");
        // keyWord = keyWord.replaceAll("\\[", "\\/[");
        // keyWord = keyWord.replaceAll("\\]", "\\/]");
        // keyWord = keyWord.replaceAll("%", "\\/%");
        // keyWord = keyWord.replaceAll("&", "\\/&");
        // keyWord = keyWord.replaceAll("_", "\\/_");
        // keyWord = keyWord.replaceAll("\\(", "\\/(");
        // keyWord = keyWord.replaceAll("\\)", "\\/)");
        return keyWord;
    }

    /**
     * 用给定的参数填满sql中的问号
     *
     * @param sql
     * @param params
     * @return
     * @throws Exception
     */
    public static String setParamsToSql(String sql, Object[] params) {
        // 健壮性 判断参数与问好是否相等
        if (params != null) {
            int count = 0;
            for (char c : sql.toCharArray()) {
                if (c == '?') {
                    count++;
                }
            }

            if (count != params.length) {
                try {
                    throw new Exception("参数数量与sql的问好数量不匹配~");
                } catch (Exception e) {
                    Yr.e(e);
                }
            }
        }
        // 开始

        for (int i = 0; i < params.length; i++) {
            sql = sql.replaceFirst("\\?", sqliteEscape(params[i].toString()));
        }
        return sql;
    }

    @SuppressLint("NewApi")
    /**
     * 将一条带有有效指针的Currsor里面的数据封装到Bean里
     * @param cursor 有效指针的cursor对象
     * @param BeanClass 目标Bean的Class
     * @return Bean
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     */
    public static <T> T parseCursorToBean(final Cursor cursor,
                                          Class<T> beanClass) throws InstantiationException,
            IllegalAccessException, NoSuchMethodException,
            IllegalArgumentException, InvocationTargetException {
        final T t = beanClass.newInstance();
        Method[] methods = beanClass.getMethods();

        for (final Method method : methods) {

            if (!method.getName().startsWith("set")) {
                continue;
            }

            final String fieldName = YBeanUtils.getFieldNameFromMethod(method);

            final int index = cursor.getColumnIndex(fieldName);

            YBeanUtils.setTypeWatcher(beanClass, fieldName, new YBeanUtils.TypeWatcher() {

                @Override
                public void onString() {

                    String v = cursor.getString(index);

                    try {
                        method.invoke(t, v);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onLongObj() {
                    Long v = cursor.getLong(index);

                    try {
                        method.invoke(t, v);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onLong() {

                    long v = cursor.getLong(index);

                    try {
                        method.invoke(t, v);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onIntObj() {

                    int v = cursor.getInt(index);

                    try {
                        method.invoke(t, v);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onInt() {

                    Integer v = cursor.getInt(index);

                    try {
                        method.invoke(t, v);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onFloatObj() {

                    Float v = cursor.getFloat(index);

                    try {
                        method.invoke(t, v);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onFloat() {
                    float v = cursor.getFloat(index);

                    try {
                        method.invoke(t, v);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onBooleanObj() {

                    int v = cursor.getInt(index);

                    try {
                        method.invoke(t, v == 1 ? true : false);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }

                }

                @Override
                public void onBoolean() {

                    int v = cursor.getInt(index);
                    try {
                        method.invoke(t, v == 1 ? true : false);
                    } catch (Exception e) {
                        Yr.logError(e);
                    }
                }

                @Override
                public void onDoubleObj() {
                }

                @Override
                public void onDouble() {
                }
            });
        }
        return t;
    }

    /**
     * 获取条数
     *
     * @return
     */
    public static int getCount(SQLiteOpenHelper helper, KeyObtainable bean) {
        return YSQLites.getCount(helper.getReadableDatabase(), bean.getClass(), YSqliteUtils.getWhere(bean));
    }

    public static int getCount(SQLiteOpenHelper helper, Class<?> beanClass,
                               String where) {
        SQLiteDatabase db = helper.getReadableDatabase();
        return getCount(db, beanClass, where);
    }

    public static int getCount(SQLiteDatabase db, Class<?> beanClass, String where) {
        String sql = YSqliteUtils.setParamsToSql(
                "select count(*) from ? where ?", beanClass.getSimpleName(),
                where);
        if (debug) {
            Yr.i("[YSQLite] getCount：" + sql);
        }
        Cursor cursor = db.rawQuery(sql, null);
        if (cursor.moveToNext()) {
            return cursor.getInt(0);
        }
        return -1;
    }

    public static class DatabaseManager {

        private AtomicInteger mOpenCounter = new AtomicInteger();

        private static DatabaseManager instance;
        private static SQLiteOpenHelper mDatabaseHelper;
        private SQLiteDatabase mDatabase;

        public static synchronized void initializeInstance(
                SQLiteOpenHelper helper) {
            if (instance == null) {
                instance = new DatabaseManager();
                mDatabaseHelper = helper;
            }
        }

        public static synchronized DatabaseManager getInstance() {
            if (instance == null) {
                throw new IllegalStateException(
                        DatabaseManager.class.getSimpleName()
                                + " is not initialized, call initializeInstance(..) method first.");
            }

            return instance;
        }

        public synchronized SQLiteDatabase openDatabase() {
            if (mOpenCounter.incrementAndGet() == 1) {
                // Opening new database
                mDatabase = mDatabaseHelper.getWritableDatabase();
            }
            return mDatabase;
        }

        public synchronized void closeDatabase() {
            if (mOpenCounter.decrementAndGet() == 0) {
                // Closing database
                mDatabase.close();
            }
        }
    }
}